package com.duoec.doc.service.impl;

import com.duoec.doc.dao.DocDao;
import com.duoec.doc.dao.DocEntityDao;
import com.duoec.doc.dao.MockDao;
import com.duoec.doc.doclet.pojo.Artifact;
import com.duoec.doc.doclet.pojo.Entity;
import com.duoec.doc.doclet.pojo.EntityRef;
import com.duoec.doc.dto.request.MockRequest;
import com.duoec.doc.dto.response.MockResponse;
import com.duoec.doc.entity.*;
import com.duoec.doc.helper.MockHelper;
import com.duoec.doc.service.ApiService;
import com.duoec.doc.service.IMocker;
import com.duoec.doc.service.MockService;
import com.duoec.doc.service.impl.mock.operator.MockOperator;
import com.google.common.collect.Lists;
import com.mongodb.client.model.Filters;
import com.mongodb.client.model.Projections;
import org.bson.conversions.Bson;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.lang.NonNull;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import javax.annotation.PostConstruct;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * @author xuwenzhen
 * @date 2020/1/31
 */
@Service
public class MockServiceImpl implements MockService {
    private static final Logger logger = LoggerFactory.getLogger(MockServiceImpl.class);

    private static final String DOC_ID = "docId";
    private static final String STR_DOC_ID = "docId";
    private static final String STR_API_ID = "apiId";
    private static final String STR_ID = "_id";
    private static final String STR_TITLE = "title";
    private static final String STR_REST_CONDITION = "restCondition";
    private static final String STR_RESPONSE_TYPE = "responseType";
    private static final String STR_DOC_VERSION = "docVersion";
    @Autowired
    private MockDao mockDao;

    @Autowired
    private DocEntityDao entityDao;

    @Autowired
    private DocDao docDao;

    @Autowired
    private ApiService apiService;

    @Autowired
    private List<IMocker> mockList;

    @Autowired
    private List<MockOperator> mockOperatorList;

    @PostConstruct
    public void init() {
        if (mockList == null || mockOperatorList == null) {
            return;
        }
        mockList.forEach(MockHelper::registryMocker);
        mockOperatorList.forEach(MockHelper::registryOperator);
    }

    /**
     * 保存mock配置
     *
     * @param mockConfig 需要保存的mock配置
     * @return 保存成功后的mock配置id
     */
    @Override
    public String save(MockConfig mockConfig) {
        String id = mockConfig.getId();
        long now = System.currentTimeMillis();
        mockConfig.setUpdateTime(now);
        if (StringUtils.isEmpty(id)) {
            //新增
            mockConfig.setCreateTime(now);
            mockDao.insertOne(mockConfig);
            return mockConfig.getId();
        } else {
            //修改
            mockDao.updateEntity(mockConfig);
            return id;
        }
    }

    /**
     * 删除某个配置
     *
     * @param docId  文档ID
     * @param apiId  接口ID
     * @param mockId 需要删除的mock配置ID
     */
    @Override
    public void delete(String docId, String apiId, String mockId) {
        mockDao.deleteOne(Filters.and(
                Filters.eq(STR_DOC_ID, docId),
                Filters.eq(STR_API_ID, apiId),
                Filters.eq(STR_ID, mockId)
        ));
    }

    /**
     * 获取某个接口的Mock配置
     *
     * @param docId 文档ID
     * @param apiId 接口ID
     * @return Mock配置简要信息列表
     */
    @Override
    public List<MockConfig> list(String docId, String apiId) {
        return mockDao
                .find(Filters.and(
                        Filters.eq(STR_DOC_ID, docId),
                        Filters.eq(STR_API_ID, apiId)
                ))
                .projection(Projections.include(STR_TITLE, STR_REST_CONDITION, STR_RESPONSE_TYPE))
                .into(Lists.newArrayList());
    }

    /**
     * 获取某个接口某个Mock配置
     *
     * @param mockId mock配置ID
     * @return Mock配置详情
     */
    @Override
    public MockConfig get(String mockId) {
        Bson filter = Filters.eq(STR_ID, mockId);
        return mockDao
                .find(filter)
                .first();
    }

    /**
     * 测试某个Mock配置
     *
     * @param mockId mock配置ID
     * @return Mock生成的数据（json）
     */
    @Override
    public String mock(String mockId) {
        MockConfig mockConfig = get(mockId);
        if (mockConfig == null) {
            return null;
        }
        return getMockResponse(mockConfig);
    }

    /**
     * 通过
     *
     * @param mockConfig mock配置
     * @return 通过此配置输出的mock返回结果（json）
     */
    @Override
    public String getMockResponse(@NonNull MockConfig mockConfig) {
        Integer responseType = mockConfig.getResponseType();
        if (responseType != null && responseType.equals(1)) {
            return mockConfig.getJsonResponse();
        }

        MockConfigItem mockItem = mockConfig.getResponse();
        if (mockItem == null || (mockItem.getAvailable() != null && !mockItem.getAvailable())) {
            return null;
        }

        IMocker mock = MockHelper.MOCKER_MAP.get(mockItem.getType());
        if (mock != null) {
            return mock.mock(mockItem);
        }
        logger.warn("未实现mocker: {}.type={}", mockItem.getEntityName(), mockItem.getType());

        return null;
    }

    /**
     * 由服务端mock starter发过来的请求
     *
     * @param request 转发请求
     * @return mock响应
     */
    @Override
    public MockResponse restMock(MockRequest request) {
        String appId = request.getAppId();
        if (StringUtils.isEmpty(appId)) {
            return null;
        }

        // 查找当前接口
        ApiEntity api = apiService.getRestApiByPath(appId, request.getPath(), request.getMethod());
        if (api == null) {
            logger.warn("找不到docId={},[{}]{}的接口定义", appId, request.getMethod(), request.getPath());
            return null;
        }

        //获取所有此接口的mock配置
        List<MockConfig> mockList = mockDao
                .find(Filters.eq(STR_API_ID, api.getKey()))
                .projection(Projections.include("_id", STR_REST_CONDITION))
                .into(Lists.newArrayList());

        if (CollectionUtils.isEmpty(mockList)) {
            return mockByApi(api);
        }

        //筛选命中的
        MockConfig mockConfig = selectMockConfig(mockList, request);
        if (mockConfig == null) {
            logger.warn("获取不到mock配置");
            return mockByApi(api);
        }
        MockResponse mockResponse = new MockResponse();

        //获取详细的配置
        String mockConfId = mockConfig.getId();
        mockConfig = get(mockConfId);
        if (mockConfig == null) {
            logger.warn("获取不到mock配置：docId={}, apiKey={}, mockConfId={}", api.getDocId(), api.getKey(), mockConfId);
            return mockResponse;
        }
        String resp = getMockResponse(mockConfig);
        mockResponse.setCode(200);
        mockResponse.setBody(resp);
        return mockResponse;
    }

    /**
     * 通过接口，获取默认的mock响应
     *
     * @param api 接口信息
     * @return mock响应
     */
    @Override
    public MockResponse mockByApi(ApiEntity api) {
        return null;
    }

    private MockResponse mockByApi2(ApiEntity api) {
        MockResponse response = new MockResponse();
        response.setCode(500);
        if (api == null) {
            logger.warn("api为空！");
            return response;
        }
        EntityRef resp = api.getResponse();
        if (resp == null) {
            logger.warn("api.response为空！");
            return response;
        }

        Artifact doc = docDao.find(Filters.eq(STR_ID, api.getDocId()))
                .projection(Projections.include(STR_DOC_VERSION))
                .first();
        if (doc == null) {
            logger.warn("无法找到doc._id={}的数据！", api.getDocId());
            return response;
        }
        Long docVersion = doc.getDocVersion();

        List<Entity> entityList = entityDao
                .find(Filters.and(
                        Filters.eq(DOC_ID, api.getDocId()),
                        Filters.eq(STR_DOC_VERSION, docVersion)
                ))
                .into(Lists.newArrayList());

        if (CollectionUtils.isEmpty(entityList)) {
            logger.warn("无法找到doc._id={}, docVersion={} 的entity数据！", api.getDocId(), docVersion);
            return response;
        }

        Map<String, Entity> entityMap = entityList.stream()
                .collect(Collectors.toMap(Entity::getName, entity -> entity));


        return null;
    }

    private MockConfig selectMockConfig(List<MockConfig> mockList, MockRequest request) {
        for (MockConfig mockConfig : mockList) {
            if (checkMockConfig(mockConfig, request)) {
                return mockConfig;
            }
        }
        return null;
    }

    private boolean checkMockConfig(MockConfig mockConfig, MockRequest request) {
        boolean match = true;
        MockRestCondition condition = mockConfig.getRestCondition();

        //请求头
        List<MockConditionItem> headerParams = condition.getHeaderParams();
        if (!CollectionUtils.isEmpty(headerParams)) {
            match = match && checkRequestParams(headerParams, request.getHeader());
        }

        //路径参数
        List<MockConditionItem> pathParams = condition.getPathParams();
        if (!CollectionUtils.isEmpty(pathParams)) {
            match = match && checkRequestParams(pathParams, request.getPathParameterMap());
        }

        //url参数
        List<MockConditionItem> urlParams = condition.getUrlParams();
        if (!CollectionUtils.isEmpty(urlParams)) {
            Map<String, String[]> parameterMap = request.getParameterMap();
            for (MockConditionItem item : urlParams) {
                if (item.getAvailable() == null || !item.getAvailable()) {
                    //没有或为false
                    continue;
                }
                String key = item.getKey();
                if (StringUtils.isEmpty(key)) {
                    continue;
                }
                String[] values = parameterMap.get(key);
                for (String value : values) {
                    if (!checkValue(value, item.getOperator(), item.getValue())) {
                        //如果未命中，则不匹配
                        match = false;
                        break;
                    }
                }
                if (!match) {
                    break;
                }
            }
        }

        return match;
    }

    private boolean checkRequestParams(List<MockConditionItem> pathParams, Map<String, String> pathParameters) {
        for (MockConditionItem item : pathParams) {
            if (item.getAvailable() == null || !item.getAvailable()) {
                //没有或为false
                continue;
            }
            String key = item.getKey();
            if (StringUtils.isEmpty(key)) {
                continue;
            }
            String value = pathParameters.get(key);
            if (!checkValue(value, item.getOperator(), item.getValue())) {
                //如果未命中，则不匹配
                return false;
            }
        }
        return true;
    }

    private boolean checkValue(String value, String operatorStr, String expectValue) {
        MockOperator operator = MockHelper.getOperator(operatorStr);
        if (operator == null) {
            logger.warn("无法找到操作符：{}", operatorStr);
            return false;
        }
        return operator.match(value, expectValue);
    }
}
